package com.oneplatform.system.service;

import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.jeesuite.common.util.AssertUtil;
import com.oneplatform.system.constants.RandomType;
import com.oneplatform.system.constants.SeqTimeExpr;
import com.oneplatform.system.dao.entity.SequenceRuleEntity;
import com.oneplatform.system.dao.mapper.SequenceRuleEntityMapper;

/**
 * 
 * <br>
 * Class Name   : SequenceGenService
 *
 * @author jiangwei
 * @version 1.0.0
 * @date 2019年10月8日
 */
@Service
public class SequenceGenService {
	
	private static String[] paddingzeros = new String[]{"","0","00","000","0000","00000","000000","0000000","00000000","000000000"};
    private static final int INCR = 100;
	private @Autowired SequenceRuleEntityMapper mapper;
	
	private Map<String, AtomicInteger> currentSequences = new HashMap<>();
	private Map<String, AtomicInteger> endSequences = new HashMap<>();
	
	public String genSequence(String appId,String code){
		SequenceRuleEntity entity = mapper.findByAppIdAndCode(appId, code);
		AssertUtil.notNull(entity);
		StringBuilder builder = new StringBuilder();
		if(StringUtils.isNotBlank(entity.getPrefix()))builder.append(entity.getPrefix());
		if(StringUtils.isNotBlank(entity.getTimeExpr())){
			builder.append(buildTimeExprSequence(entity.getTimeExpr()));
		}
		
		builder.append(buildIncrNumSequence(appId,code,entity.getSeqLength()));
		
		if(entity.getRandomLength() > 0){
			builder.append(RandomStringUtils.random(entity.getRandomLength(), RandomType.chars(entity.getRandomType()), RandomType.numbers(entity.getRandomType())));
		}
		return builder.toString();
	}

	/**
	 * @param timeExpr
	 * @return
	 */
	private String buildTimeExprSequence(String timeExpr) {
		String seq = timeExpr;
		Calendar calendar = Calendar.getInstance();
		if(timeExpr.contains(SeqTimeExpr.YEAR.getExpr())){
			seq = seq.replace(SeqTimeExpr.YEAR.getExpr(), String.valueOf(calendar.get(Calendar.YEAR)));
		}
		if(timeExpr.contains(SeqTimeExpr.MONTH.getExpr())){
			int month = calendar.get(Calendar.MONTH) + 1;
			seq = seq.replace(SeqTimeExpr.MONTH.getExpr(), month > 9 ? String.valueOf(month) : ("0" + month));
		}
        if(timeExpr.contains(SeqTimeExpr.DAY.getExpr())){
        	int date = calendar.get(Calendar.DATE);
			seq = seq.replace(SeqTimeExpr.DAY.getExpr(), date > 9 ? String.valueOf(date) : ("0" + date));
		}
        if(timeExpr.contains(SeqTimeExpr.HOUR.getExpr())){
			seq = seq.replace(SeqTimeExpr.HOUR.getExpr(), String.valueOf(calendar.get(Calendar.HOUR_OF_DAY)));
		}
        if(timeExpr.contains(SeqTimeExpr.MINUTE.getExpr())){
			seq = seq.replace(SeqTimeExpr.MINUTE.getExpr(), String.valueOf(calendar.get(Calendar.MINUTE)));
		}
        if(timeExpr.contains(SeqTimeExpr.SECOND.getExpr())){
			seq = seq.replace(SeqTimeExpr.SECOND.getExpr(), String.valueOf(calendar.get(Calendar.SECOND)));
		}
		return seq;
	}
	
	/**
	 * @param code
	 * @param seqLength
	 * @return
	 */
	private String buildIncrNumSequence(String appId,String code, Integer seqLength) {
		if(!currentSequences.containsKey(code) || currentSequences.get(code).get() >= endSequences.get(code).get()){
			loadFormDb(appId,code);
		}
		String seq = String.valueOf(currentSequences.get(code).incrementAndGet());
		if(seq.length() > seqLength){
			int next = currentSequences.get(code).updateAndGet( (x) -> String.valueOf(x + 1).length() > seqLength ? 1 : x + 1);
			seq = String.valueOf(next);
		}
		//补0
		int len = seqLength - seq.length();
		if(len > 0){
			seq = paddingzeros[len] + seq;   
		}
		return seq;
	}

	/**
	 * @param code
	 * @return
	 */
	private synchronized void loadFormDb(String appId,String code) {
		
		if(currentSequences.containsKey(code) && (currentSequences.get(code).get() < endSequences.get(code).get())){
			return;
		}
		
		Integer lastSequence = 0;
		int updated = 0;
		int count = 0;
		//乐观锁
		while(true){
			lastSequence = mapper.findLastSequence(appId, code);
			updated = mapper.updateLastSequence(appId,code, INCR, lastSequence);
			if(updated > 0)break;
			if(count++ > 1){
				try {Thread.sleep(200);} catch (Exception e) {}
			}
		}
		
		if(!currentSequences.containsKey(code)){
			currentSequences.put(code, new AtomicInteger(lastSequence));
			endSequences.put(code, new AtomicInteger(lastSequence + INCR));
		}else{
			currentSequences.get(code).set(lastSequence);
			endSequences.get(code).set(lastSequence + INCR);
		}
	}
}
